﻿using System;
using System.IO;
using System.Linq;
using System.Security;
using System.Security.Cryptography;
using System.Security.Permissions;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;
 
using System.Reflection;
using Microsoft.Security.Application;

namespace Kaiser.Extensions
{
    public static class StringExtensions
    {
        private static readonly Regex s_tagRegex = new Regex("<[^<>]*>", RegexOptions.Compiled | RegexOptions.Singleline);
        private static readonly Regex s_guidRegex = new Regex(@"^(\{){0,1}[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}(\}){0,1}$", RegexOptions.Compiled | RegexOptions.Singleline);
        private static readonly Regex s_spaceRegex = new Regex(@"\s+", RegexOptions.Compiled | RegexOptions.Singleline);
        private static readonly Regex s_nonWordCharsRegex = new Regex(@"[^\w]+", RegexOptions.Compiled | RegexOptions.Singleline);
        private static readonly Regex s_uriRegex = new Regex("(^|[^\\w'\"]|\\G)(?<uri>(?:https?|ftp)(?:&#58;|:)(?:&#47;&#47;|//)(?:[^./\\s'\"<)\\]]+\\.)+[^./\\s'\"<)\\]]+(?:(?:&#47;|/).*?)?)(?:[\\s\\.,\\)\\]'\"]?(?:\\s|\\.|\\)|\\]|,|<|$))", RegexOptions.Compiled | RegexOptions.IgnoreCase);
        private static readonly Regex s_subDirectoryRegex = new Regex(@"^[a-zA-Z0-9-_]+(/[a-zA-Z0-9-_]+)*$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
        private static readonly Regex s_virtualDirectoryRegex = new Regex(@"^~(/[a-zA-Z0-9-_]+)+$", RegexOptions.Compiled | RegexOptions.IgnoreCase);

        #region Guid相关

        /// <summary>
        /// 校验字符串是否是Guid格式
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        public static bool IsGuid(this string source)
        {
            return !source.IsNullOrWhiteSpace() && s_guidRegex.IsMatch(source);            
        }
        /// <summary>
        /// 字符串转换为Guid
        /// </summary>
        /// <param name="source"></param>
        /// <param name="result"></param>
        /// <returns></returns>
        public static bool GuidTryParse(this string source, out Guid result)
        {
            if (source.IsNullOrWhiteSpace())
            {
                result = Guid.Empty;
                return false;
            }

            try
            {
                result = new Guid(source);
                return true;
            }
            catch (FormatException)
            {
                result = Guid.Empty;
                return false;
            }
            catch (OverflowException)
            {
                result = Guid.Empty;
                return false;
            }
            catch (Exception)
            {
                result = Guid.Empty;
                return false;
            }
        }

        #endregion

        #region 字符串清理

        /// <summary>
        /// 清除字符串的Html标签
        /// </summary>
        /// <param name="source">源字符串</param>
        /// <returns></returns>
        public static string CleanHtmlTags(this string source)
        {
            return source.CleanHtmlTags(null);
        }

        /// <summary>
        /// 清除字符串的Html标签
        /// </summary>
        /// <example>
        /// <para>如：CleanHtmlTags(source,"a")则不清理a标签</para>
        /// <para>如：CleanHtmlTags(source,"a|br")则不清理a标签和br标签</para>
        /// </example>
        /// <param name="source">源字符串</param>
        /// <param name="exceptionPattern">要排除的标签</param>
        /// <returns></returns>
        public static string CleanHtmlTags(this string source, string exceptionPattern)
        {
            if (!string.IsNullOrEmpty(exceptionPattern))
                return
                    new Regex(string.Format("<(?!{0})[^<>]*>", exceptionPattern),
                              RegexOptions.Compiled | RegexOptions.Singleline).Replace(source, String.Empty);

            return s_tagRegex.Replace(source, String.Empty);
        }

        /// <summary>
        /// 清理空格，包括空格、制表符、换页符等
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        public static string CleanWhitespace(this string source)
        {
            return s_spaceRegex.Replace(source, String.Empty);
        }

        public static string CleanCssClassName(this string source)
        {
            return s_nonWordCharsRegex.Replace(source, "_").ToLower(System.Globalization.CultureInfo.CurrentCulture);
        }

        public static string CleanText(this string source)
        {
            if (source == null) return null;

            return AntiXss.HtmlEncode(source);
        }

        public static string CleanHtml(this string source)
        {
            //AntiXss library from Microsoft 
            //(http://antixss.codeplex.com)
            string encodedText = AntiXss.HtmlEncode(source);
            //convert line breaks into an html break tag
            return encodedText.Replace("&#13;&#10;", "<br />");
        }

        public static string CleanForQueryString(this string source)
        {
            return AntiXss.UrlEncode(source);
        }

        public static string CleanAttribute(this string source)
        {
            return AntiXss.HtmlAttributeEncode(source);
        }

 
        // the cleaning power of CleanAttribute (everything should but AntiXss.HtmlAttributeEncode encodes 
        // *everyting* incl. white space :|) so attributes can get really long...but then my only current worry is around
        // the description meta tag. Attributes from untrusted sources *do* need the current CleanAttribute...
        public static string CleanHref(this string source)
        {
            return HttpUtility.HtmlAttributeEncode(source);
        }

        public static string CleanCommentBody(this string source)
        {
            return source.CleanHtmlTags().CleanHtml().AutoAnchor();
        }

        #endregion

        public static string AutoAnchor(this string source)
        {
            MatchCollection uriMatches = s_uriRegex.Matches(source);

            foreach (Match uriMatch in uriMatches)
            {
                string encodedUri = uriMatch.Groups["uri"].Value;

                if (!string.IsNullOrEmpty(encodedUri))
                {
                    string uri = HttpUtility.HtmlDecode(encodedUri);
                    source = source.Replace(encodedUri, string.Format("<a href=\"{0}\">{1}</a>", uri.CleanHref(), uri.CleanText()));
                }
            }

            return source;
        }

        public static string Shorten(this string source, int characterCount)
        {
            string text = !string.IsNullOrEmpty(source) ? source.CleanHtmlTags().CleanWhitespace() : String.Empty;

            if (!string.IsNullOrEmpty(text) && characterCount > 0 && text.Length > characterCount)
            {
                text = text.Substring(0, characterCount);
            }

            return text;
        }
        public static string Substr(this string source, int len)
        {
            return source.Substr(len, "...");
        }
        public static string Substr(this string source, int len, string att)
        {

            if (!string.IsNullOrEmpty(source))
            {
                att = att ?? String.Empty;

                Regex rChinese = new Regex(@"[\u4e00-\u9fa5]"); //验证中文

                Regex rEnglish = new Regex(@"^[A-Za-z0-9]+$");  //验证字母

                if (rChinese.IsMatch(source))
                {
                    //中文
                    return (source.Length > len) ? source.Substring(0, len) + att : source;

                }

                else if (rEnglish.IsMatch(source))
                {
                    //英文
                    return (source.Length > len * 2) ? source.Substring(0, len * 2) + att : source;

                }
                return (source.Length > len) ? source.Substring(0, len) + att : source;

            }

            return String.Empty;

        }
        public static string Ellipsize(this string source, int characterCount, Func<string, string> processStringPart)
        {
            return source.Ellipsize(characterCount, processStringPart, "&#160;&#8230;");
        }
        public static string Ellipsize(this string source, int characterCount, Func<string, string> processStringPart, string ellipsis)
        {
            ++characterCount;
            string text = !string.IsNullOrEmpty(source) ? source.CleanHtmlTags().CleanWhitespace() : String.Empty;

            if (string.IsNullOrEmpty(text) || characterCount < 1 || text.Length <= characterCount)
                return text;

            string[] words = text.Substring(0, characterCount).Split(' ');

            return processStringPart(string.Join(" ", words.Take(words.Length - 1).ToArray())) + ellipsis;
        }
        public static string EllipsizeUri(this string source, int characterCount, Func<string, string> processStringPart)
        {
            return source.EllipsizeUri(characterCount, processStringPart, "&#160;&#8230;&#160;");
        }
        //info: (nheskew) ellipsis length hard-coded to the default decoded
        public static string EllipsizeUri(this string source, int characterCount, Func<string, string> processStringPart, string ellipsis)
        {
            Uri uri;
            int ellipsisLength = 3; // not really accurate considering the use of the hellip character

            // return because we're not going to mess with the "URI" string
            if (string.IsNullOrEmpty(source) ||
                characterCount < 1 ||
                source.Length <= characterCount ||
                !Uri.TryCreate(source, UriKind.Absolute, out uri))
                return processStringPart(source);

            string start = uri.Scheme + "://";
            string end = uri.Segments.LastOrDefault() ?? String.Empty;

            if (!string.IsNullOrEmpty(uri.Query))
                end = end + "?" + ellipsis;

            // need to ellipsize the host name because the string is already getting too long
            if (start.Length + uri.Host.Length + ellipsisLength + end.Length > characterCount)
            {
                string host = uri.Host;
                int endLength = characterCount - (start.Length + host.Length + ellipsisLength);

                if (endLength < 0)
                {
                    int hostSubLength = (characterCount - (start.Length + ellipsisLength * 2)) / 2; // two ellilpsis. host and end

                    host = hostSubLength > 0
                        ? processStringPart(host.Substring(0, hostSubLength)) +
                            ellipsis +
                            processStringPart(host.Substring(host.Length - hostSubLength, hostSubLength))
                        : String.Empty;

                    endLength = 0;
                }
                else
                {
                    host = processStringPart(host);
                }

                return processStringPart(start) +
                       host +
                       ellipsis +
                       (endLength > 0 ? processStringPart(end.Substring(end.Length - endLength, endLength)) : String.Empty);
            }

            start = start + uri.Host;

            // add as many path segments as we can
            var pathParts = uri.Segments.Take(uri.Segments.Length - 1);
            foreach (string pathPart in pathParts)
            {
                if (start.Length + pathPart.Length + ellipsisLength + end.Length > characterCount)
                    return processStringPart(start) + ellipsis + processStringPart(end);

                start = start + pathPart;
            }

            return processStringPart(start + end);
        }

        #region 文件读写
        public static string GetFileText(this string virtualPath)
        {
            return virtualPath.GetFileText(new HttpContextWrapper(HttpContext.Current));
        }
        public static string GetFileText(this string virtualPath, HttpContextBase httpContext)
        {
            string path = httpContext.Server.MapPath(virtualPath);

            if (File.Exists(path))
                return File.ReadAllText(path);

            return null;
        }
        public static string GetFileName(this string virtualPath)
        {
            return Path.GetFileNameWithoutExtension(virtualPath);
        }
        public static void SaveFileText(this string virtualPath, string code)
        {
            virtualPath.SaveFileText(code, new HttpContextWrapper(HttpContext.Current));
        }
        public static void SaveFileText(this string virtualPath, string code, HttpContextBase httpContext)
        {
            string path = httpContext.Server.MapPath(virtualPath);

            if (path.IsFileWritable())
                File.WriteAllText(path, code);
        }
        public static bool IsFileWritable(this string filePath)
        {
        
            return !filePath.IsNullOrWhiteSpace()
                && File.Exists(filePath)
                && (File.GetAttributes(filePath) & FileAttributes.ReadOnly) != FileAttributes.ReadOnly;
                //&& SecurityManager.IsGranted(new FileIOPermission(FileIOPermissionAccess.Write, filePath));

        }
        public static bool IsFileWritable(this string virtualPath, HttpContextBase httpContext)
        {
            return IsFileWritable(httpContext.Server.MapPath(virtualPath));
        }
        public static DateTime? FileModifiedDate(this string filePath)
        {
            if (!string.IsNullOrEmpty(filePath) && File.Exists(filePath))
                return File.GetLastWriteTime(filePath);

            return null;
        }
        public static DateTime? FileModifiedDate(this string virtualPath, HttpContextBase httpContext)
        {
            return httpContext.Server.MapPath(virtualPath).FileModifiedDate();
        }
        #endregion

        #region 字符串空/null校验
        public static bool IsNullOrEmpty(this string source)
        {
            return String.IsNullOrEmpty(source);
        }
        public static bool IsNullOrWhiteSpace(this string source)
        {
            return string.IsNullOrWhiteSpace(source);
        }
        #endregion

        #region 字符串格式化

        public static string FormatWith(this string format, object arg0)
        {
            return String.Format(format, arg0);
        }
        public static string FormatWith(this string format, object arg0, object arg1)
        {
            return String.Format(format, arg0, arg1);
        }
        public static string FormatWith(this string format, object arg0, object arg1, object arg2)
        {
            return String.Format(format, arg0, arg1, arg2);
        }
        public static string FormatWith(this string format, params object[] args)
        {
            return String.Format(format, args);
        }
        public static string FormatWith(this string format, IFormatProvider provider, params object[] args)
        {
            return String.Format(provider, format, args);
        }

        #endregion

        /// <summary>
        /// 字符串重复
        /// </summary>
        /// <param name="source">源字符串</param>
        /// <param name="times">重复次数</param>
        /// <returns></returns>
        public static string Repeat(this string source, int times)
        {
            if (String.IsNullOrEmpty(source) || times <= 0)
                return source;
            StringBuilder sb = new StringBuilder();
            while (times > 0)
            {
                sb.Append(source);
                times--;
            }
            return sb.ToString();
        }

        /// <summary>
        /// 简单过滤SQL语句
        /// </summary>
        /// <param name="sqlString"></param>
        /// <returns></returns>
        public static string SqlFilter(this string sqlString)
        {
            if (sqlString == null)
                return null;
            sqlString = sqlString.ToLower();
            string words = "and|exec|insert|select|delete|update|chr|mid|master|or|truncate|char|declare|join";
            foreach (string i in words.Split('|'))
            {
                if ((sqlString.IndexOf(i + " ") > -1) || (sqlString.IndexOf(" " + i) > -1))
                {
                    sqlString = sqlString.Replace(i,String.Empty);
                }
            }
            return sqlString;
        }

        /// <summary>
        /// 确保目录为子目录
        /// </summary>
        /// <example>
        /// <para>~/目录名/ -> 目录名</para>
        /// <para>~/目录名/子目录名/ -> 目录名/子目录名</para>
        /// </example>
        /// <param name="path"></param>
        /// <returns></returns>
        public static string EnsureSubFolder(this string path)
        {
            if (String.IsNullOrWhiteSpace(path))
                throw new ArgumentNullException("path");

            if (path.StartsWith("~"))
                path = path.Substring(1, path.Length - 1);

            string[] pathNames = path.Split('/');
            path = string.Empty;

            foreach (string p in pathNames)
            {
                if (p != string.Empty)
                    path += p + "/";
            }
            if (path.EndsWith("/"))
            {
                path = path.Substring(0, path.Length - 1);
            }
            return path;
        }

        /// <summary>
        /// 确保目录为根目录
        /// </summary>
        /// <example>
        /// <para>目录名/ -> ~/目录名</para>
        /// <para>/目录名/子目录名/ -> ~/目录名/子目录名</para>
        /// </example>
        /// <param name="path"></param>
        /// <returns></returns>
        public static string EnsureVirtualDirectory(this string path)
        {
            if (String.IsNullOrEmpty(path))
                throw new ArgumentNullException("path");

            if (!path.StartsWith("~/"))
            {
                if (!path.StartsWith("/"))
                    path = "/" + path;
                if (!path.StartsWith("~"))
                    path = "~" + path;
            }
            if (path.EndsWith("/"))
            {
                path = path.Substring(0, path.Length - 1);
            }
            return path;
        }

        /// <summary>
        /// 判断目录是否为子目录
        /// </summary>
        /// <param name="path"></param>
        /// <returns><c>true</c>是子目录；<c>false</c>不是子目录</returns>
        public static bool IsSubDirectory(this string path)
        {
            return !path.IsNullOrWhiteSpace() && s_subDirectoryRegex.IsMatch(path); 
        }

        /// <summary>
        /// 判断目录是否为虚拟目录
        /// </summary>
        /// <param name="path"></param>
        /// <returns><c>true</c>是虚拟目录；<c>false</c>不是虚拟目录</returns>
        public static bool IsVirtualDirectory(this string path)
        {
            return !path.IsNullOrWhiteSpace() && s_virtualDirectoryRegex.IsMatch(path);
        }
    }
}
